package chloroform

import argo.jdom.JsonNodeBuilders._
import collection.mutable.HashMap
import l10n.ProjectLocale
import util.JsonRendererUtil
import javax.servlet.http.HttpServletRequest
import java.lang.{UnsupportedOperationException, String}
import collection.immutable.List
import java.util.{Map, Locale}
import argo.jdom._
import scaladuct._

/**
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: 7.3.11
 * Time: 15:43
 * To change this template use File | Settings | File Templates.
 */

@serializable
@SerialVersionUID(1L)
class ProjectConfigSession(pageRegister: ConfigPageRegister, metaPageRegister: MetaConfigPageRegister,
                           projectRef: ProjectRef) extends MessageHandler {

  private val cache = new HashMap[String, List[JsonObjectNodeBuilder]]()

  protected def getConfigPage[X](pageName: String, constructJsonBuilders: Boolean = false, extendedPage: ConfigPage[_]): ConfigPage[X] =
    if (extendedPage == null)
      pageRegister.getConfigPage(pageName, true, constructJsonBuilders, null, Nil, true)
    else
      pageRegister.getConfigPage(pageName, true, constructJsonBuilders, null, Nil, false, extendedPage)

  def handleMessage(message: JsonRootNode): MessageOutcome = {
    val clientMsgType: String = message.getStringValue("msgType")

    import ProjectConfigSessionFactory.MessageType._
    values.find(_.toString == clientMsgType) match {
      case None => throw new UnsupportedOperationException("Unknown message type " + clientMsgType)

      case Some(messageType) => messageType match {
        case GetPreConfigCookies => {
          val pageName = message.getStringValue("pageName")
          JsonResponse(getPreConfigCookies(pageName, null)._1)
        }
        case GetAllPreConfigCookies => {
          JsonResponse(getAllPreConfigCookies(message.getStringValue("scenario")))
        }
        case UpdatePreConfigCookieProperty => {
          JsonResponse(updatePreConfigCookieProperty(message.getStringValue("pageName"),
            Integer.parseInt(message.getNumberValue("cookieIndex")),
            message.getStringValue("propertyName"),
            message.getFields.get(JsonNodeFactories.aJsonString("value")))
          )
        }
        case SaveProject => {
          JsonResponse(saveProject())
        }
        case GetMetaProjectEntries => {
          JsonResponse(getMetaProjectEntries())
        }
        case AddProjectEntry => {
          JsonResponse(addProjectEntry(message.getStringValue("metaPageName"), message.getStringValue("pageName"),
            message.getStringValue("scenario"), message.getArrayNode("preconfigured")))
        }
        case RemoveEntry => {
          JsonResponse(removeProjectEntry(message.getStringValue("pageName")))
        }
        case MoveExtension => {
          JsonResponse(moveExtension(message.getStringValue("extId"),
            Integer.parseInt(message.getNumberValue("delta"))))
        }
      }
    }

    //    val clientMsgType: String = response.getNullableStringValue("msgType")
    //    clientMsgType match {
    //      case "GetPreConfigCookies" => {
    //        val pageName = response.getStringValue("pageName")
    //        getPreConfigCookies(pageName)
    //      }
    //      case _ => throw new UnsupportedOperationException("Unknown message type " + clientMsgType)
    //    }
  }

  private def getMetaProjectEntries() = {
    val metaPageNames = metaPageRegister.getConfigPageNames

    // reply
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("MetaProjectEntries"))
    val entries = anArrayBuilder
    metaPageNames.foreach(metaPageName => {
      val entryRowBuilder = anArrayBuilder
      entryRowBuilder.withElement(aStringBuilder(metaPageName))
      entryRowBuilder.withElement(aStringBuilder("todo"))
      entries.withElement(entryRowBuilder)
    })

    root.withField("entries", entries)
    root
  }

  private def addProjectEntry(metaPageName: String, pageName: String, scenario: String,
                              preconfigured: java.util.List[JsonNode]) = {
    val project: Project with ProjectLocale = projectRef.getProject

//    val starter: Starter[ConfigPageFactory[_]] = metaPageRegister.getStarter(metaPageName)
//    val newPage: ConfigPage[_] = starter.newInstance.createConfigPage(pageName, pageRegister)

    //    if (newPage.getCookie == Terminal) {
    //      // todo
    //    } else {

    project.extension(classOf[MutableProject]) match {
      case Some(mutableProject) => {
        val preConfigCookies = JsonProjectCookie(preconfigured)

        val pathSteps: Array[String] = pageName.split(",").reverse
        val entryName = pathSteps.head

        val newEntry = new ProjectEntry(entryName, metaPageName, true, Set(scenario), preConfigCookies, Nil)
        if (pathSteps.size == 1) {
          mutableProject.addEntry(newEntry, false)
        } else {
          val extPath = pathSteps.tail.reduceLeft((agg, step) => {
            step + "," + agg
          })
          val extEntry = project.findInPath(extPath)
          extEntry.addExtension(newEntry)
        }

      }
      case _ => error("Project is imutable")
    }
    //    }

    // reply
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("ProjectEntryAdded"))
    root
  }

  private def removeProjectEntry(entryName: String) = {
    projectRef.getProject.extension(classOf[MutableProject]) match {
      case Some(mutableProject) => {
        mutableProject.removeInPath(entryName)
      }
      case _ => error("Project is imutable")
    }

    // reply
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("ProjectEntryRemoved"))
    root
  }

  private def moveExtension(extId: String, delta: Int) = {
    projectRef.getProject.extension(classOf[MutableProject]) match {
      case Some(mutableProject) => {
        mutableProject.findInPath(extId).extendedEntry.moveExtension(extId, delta)
      }
      case _ => error("Project is imutable")
    }

    // reply
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("ExtensionMoved"))
    root
  }

  private def saveProject() = {
    projectRef.getProject.extension(classOf[ProjectSaver]) match {
      case Some(savable) => {
        savable.save
      }
      case None =>
    }

    // reply
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("ProjectSaved"))
    root

    // todo: in case of save error return ProjectSaveError message
  }

  private def updatePreConfigCookieProperty(pageName: String, cookieIndex: Int, propertyName: String,
                                            valueNode: JsonNode): JsonObjectNodeBuilder = {
    val project = projectRef.getProject
    val entry = project.findInPath(pageName).asInstanceOf[TypedProjectEntry]
    entry.cookies(cookieIndex).setProperty(propertyName, valueNode)

    entry.reloadEntryType

    // reply
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("PreConfigCookiePropertyUpdated"))
    root

    // todo: in case of update error return PreConfigCookiePropertyUpdateError message
  }

  private def getAllPreConfigCookies(scenario: String): JsonObjectNodeBuilder = {
    val project = projectRef.getProject
    val entries: List[ProjectEntry] = project.getEntriesForScenario(scenario)
    val root = anObjectBuilder
    root.withField("msgType", aStringBuilder("EditAllPreConfigCookies"))
    val entriesRoot = anObjectBuilder
    root.withField("entries", entriesRoot)

    entries.foreach(entry => {

      def addBuilderForEntry(entryFullName: String, entry: ProjectEntry, extendedPage: ConfigPage[_]) {
        val (preCookiesForEntryBuilder, page) = getPreConfigCookies(entryFullName, extendedPage)
        entriesRoot.withField(entryFullName, preCookiesForEntryBuilder)

        entry.extensions.foreach(extEntry => {
          addBuilderForEntry(entryFullName + "," + extEntry.name, extEntry, page)
        })

      }

      addBuilderForEntry(entry.name, entry, null)

//      val preCookiesForEntryBuilder = getPreConfigCookies(entry.name)
//      entriesRoot.withField(entry.name, preCookiesForEntryBuilder)

    })

    root
  }

  private def getPreConfigCookies(pageName: String, extendedPage: ConfigPage[_]): (JsonObjectNodeBuilder, ConfigPage[_]) = {
    val root: JsonObjectNodeBuilder = anObjectBuilder
    root.withField("msgType", aStringBuilder("EditPreConfigCookies"))

    val (preConfigCookies, page) = getPreConfiguredCookiesForPage(pageName, extendedPage)
    val cookieArray = anArrayBuilder
    preConfigCookies.foreach(cookieBuilder => {
      cookieArray.withElement(cookieBuilder)
    })

    root.withField("cookies", cookieArray)

    (root, page)
  }

  private def getPreConfiguredCookiesForPage(pageName: String, extendedPage: ConfigPage[_]):
  (List[JsonObjectNodeBuilder], ConfigPage[_]) = {
    val page = getConfigPage(pageName, true, extendedPage).asInstanceOf[PreConfiguredPage[_]]
    (page.getJsonCookieBuilders, page)

    //    cache.synchronized {
    //      cache.get(pageName) match {
    //        case None => {
    //          val page = getConfigPage(pageName).asInstanceOf[PreConfiguredPage]
    //          val cookieJsonBuilders = page.getJsonCookieBuilders
    //          cache += (pageName -> cookieJsonBuilders)
    //          cookieJsonBuilders
    //        }
    //        case Some(cookieJsonBuilders) => cookieJsonBuilders
    //      }
    //    }
  }

}

class ProjectConfigSessionFactory extends MessageHandlerFactory {

  type MessageHandlerType = ProjectConfigSession

  object MessageType extends MessageTypeEnum {
    type MessageType = Value
    val GetPreConfigCookies, GetAllPreConfigCookies, UpdatePreConfigCookieProperty, SaveProject,
    GetMetaProjectEntries, AddProjectEntry, RemoveEntry, MoveExtension = Value
  }

  val getHandledMessageIds = MessageType

  protected def createSession(lookup: ConfigPageRegisterLookup, req: HttpServletRequest): ProjectConfigSession = {
    new ProjectConfigSession(lookup.getConfigPageRegister(req), lookup.getMetaRegister,
      lookup.getProjectRef)
  }

  def getHandler(messageType: getHandledMessageIds.Value, message: JsonRootNode, req: HttpServletRequest) = {
    val session = req.getSession

    var preConfigSession: ProjectConfigSession = null
    session.synchronized {
      //preConfigSession = session.getAttribute(classOf[ProjectConfigSession].getName).asInstanceOf[ProjectConfigSession]
      preConfigSession = session.getAttribute(getClass.getName).asInstanceOf[ProjectConfigSession]
      if (preConfigSession == null) {
        //        var projectName = if (message.isStringValue("projectName"))
        //          message.getStringValue("projectName")
        //        else
        //          null
        //        if (projectName == null || "" == projectName) {
        //          //projectName = ProjectRef(session).projectName
        //          projectName = ConfigPageRegisterLookup.get(session).get
        //        }
        //        val configPageRegister = ConfigPageRegisterLookup.getConfigPageRegister(projectName, req)
        val lookup: ConfigPageRegisterLookup = ConfigPageRegisterLookup.get(session)
        preConfigSession = createSession(lookup, req)
        //session.setAttribute(classOf[ProjectConfigSession].getName, preConfigSession)
        session.setAttribute(getClass.getName, preConfigSession)
      }
    }
    preConfigSession
  }

  def returnHandler(handler: MessageHandlerType, req: HttpServletRequest): Unit = {
    req.getSession.setAttribute(getClass.getName, handler)
  }

}

object ProjectConfigSessionFactory extends ProjectConfigSessionFactory

object MetaProjectConfigSessionFactory extends ProjectConfigSessionFactory {
  override protected def createSession(lookup: ConfigPageRegisterLookup, req: HttpServletRequest) = {
    new ProjectConfigSession(lookup.getMetaRegister, lookup.getMetaMetaRegister, lookup.getMetaProjectRef)
  }
}
